iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 15
0
自我挑戰組

Metaprogramming Ruby and Rails系列 第 15

Day 15 -- Blocks in Ruby Part II

  • 分享至 

  • xImage
  •  

Scope Gates

Scope gate 可以想像為柵欄,因為柵欄讓作用域分隔兩邊的變數既不看見彼此,也無法互通。在 Ruby 裡有三個主要的 scope gates :

  1. Class definitions
  2. Module definitions
  3. Methods

當程式進入或退出類別、模組或是方法時,scope 也會跟著改變。關鍵字像是: class / module / def 都可想像為作用域的邊界關口,而 end 就類似出口的概念。在書中 class / module / def 都稱為是 scope gate。

回到昨天的最後ㄧ個範例:

#  可使用 local_variables 取得當下區域變數的值
v1 = 1
self                       # main
              
class MyClass              # SCOPE GATE: entering class
  v2 = 2
  local_variables          # => [:v2]
  
  def my_method           # SCOPE GATE: entering def
    v3 = 3
    local_variables
  end                     # SCOPE GATE: leaving def
  
  local_variables         # => [:v2]
  
end                       # SCOPE GATE: leaving def

obj = MyClass.new
obj.my_method             # => [:v3]
obj.my_method             # => [:v3]
local_variables           # => [:v1, :obj]˙

把 SCOPE GATE 標示之後,就能非常清楚地了解在哪ㄧ行程式碼的時候會進入或是退出 scope,什麼變數和方法是可以使用的。要注意的是:class 和 module 都是立刻就執行的,但方法只有在被呼叫時,才會被執行。

Flattening the Scope (nested lexical scopes)

雖然 scope gates 能夠做好把關,不讓其他作用域的變數進入。但是 Ruby 偷偷開了後門,換個方式也是能夠偷渡變數進入的。其實這跟 private 的情況有點像,基本上 private 方法是不被允許使用的,但如果用 send() 方法的話就可以了。

class Cat
  private
  
  def eat
    puts "a_private_method"
  end
end
  
cat = Cat.new
cat.eat           # => private method `eat' called…
cat.send(:eat)    # => "a_private_method" 

回歸到正題,如果我們能夠以某種方式避開 class 以及 def 的關鍵字,或許就可以偷渡外面 top-level variables 進入到類別裡。

my_var = "Success"
class MyClass
  # 希望可以使用 my_var 在 MyClass 的 scope
  puts "#{my_var} in the class definition"
  
  def my_method
    # 希望可以使用 my_var 在 my_method 的 scope
    puts "#{my_var} in the method definition"
  end
end

解決方式其實不難

  • Class / Module: 可以用 Class.new 取代原有的 class ,並且讓 block 傳進到 Class.new 來定義實例方法。
  • Def: 至於 def 的方法就利用之前在介紹動態方法過的 define_method。
my_var = "Success"
MyClass = Class.new do 

  puts "#{my_var} in the class definition"
  
  define_method :my_method do
    puts "#{my_var} in the method definition"
  end
end

MyClass.new.my_method

如果兩個作用域(scope)擠壓合併下,能夠共同分享變數。我們就稱此黑魔法為 " Flat Scope "。

Sharing the Scope

另一個運用 Dynamic Dispatch 存取 Kernel#define_method 創造出 (shared scope) 共享作用域的技巧。

def define_methods
  shared = 0
  
  Kernel.send :define_method, :counter do 
    shared
  end
  
  Kernel.send :define_method, :inc do |x| 
    shared += x
  end
end

define_methods

counter # => 0 
inc(4)
counter # => 4

總結

  • 每一個 Ruby 的作用域都包含ㄧ堆 bindings,而作用域都是被三個 Scope Gates 所隔開:(1) class 、(2) module、(3) def。
  • 如果你需要偷渡 bindings 到不同的 scope,則需要避開 scope gates 的關鍵字,可以用 Class.new 替換 class、Module.new 替換 module、Module#define_method 替換 def。
  • 如果你需要共享 local bindngs 在許多方法中,則可以考慮 shared scoped 的技巧。

上一篇
Day 14 -- Blocks in Ruby Part I
下一篇
Day 16 -- Callable Object: Proc 、Lambda 、Method
系列文
Metaprogramming Ruby and Rails33
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言